﻿using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using SPContrib.Artifacts.Collections;
using SPContrib.Extensions;

namespace SPContrib.Artifacts.Definitions
{
    /// <summary>
    /// TODO: document
    /// </summary>
    public class ContentTypeDefinition : ElementManifestDefinition
    {
        private ContentTypeDefinitionCollection _parent;
        private FieldRefDefinitionCollection _fieldrefs;
        private SPContentType _ctype; 

        public SPContentTypeId Id { get; private set; }
        public string Name { get; private set; }
        public string Group { get; private set; }
        public string Description { get; private set; }

        /// <summary>
        /// TODO: document
        /// </summary>
        public SPContentType SPContentType
        {
            get { return _ctype; }
        }

        internal SPFieldCollection SiteColumns
        {
            get
            {
                return _parent.SiteColumns;
            }
        }

        private ContentTypeDefinition(ContentTypeDefinitionCollection parent)
        {
            this._parent = parent;            
        }

        internal ContentTypeDefinition(ContentTypeDefinitionCollection parent, SPElementDefinition definition)
            :this(parent)
        {
            var xelement = definition.AsXml();                               

            this.Id = new SPContentTypeId(xelement.Attribute<string>("ID"));
            this.Name = xelement.Attribute<string>("Name");
            this.Group = xelement.Attribute<string>("Group");
            this.Description = xelement.Attribute<string>("Description");

            this._fieldrefs = new FieldRefDefinitionCollection(this, xelement.Element("FieldRefs").Elements());

            // TODO: add XmlDocs

            this._ctype = _parent.FindContentTypeByDefinition(this);

        }

        private void SynchronizeProperties(SPContentType ctype)
        {
            ctype.Name = this.Name;
            ctype.Group = this.Group;
            ctype.Description = this.Description;
            Update(ctype);
        }

        private void SortFieldRefs(SPContentType ctype)
        {
            // TODO: add sorting

           //Update(ctype);
        }

        private void SynchronizeFieldRefs(SPContentType ctype)
        {
            if (ctype.IsListContentType())
            {
                // remove all the fieldrefs first if it´s a ctype used on a list
                _fieldrefs.ForEach(frd =>
                {
                    ctype.FieldLinks.Delete(frd.Id);
                });

                Update(ctype);
                SynchronizeFieldRefsOnListContentType(ctype);
            }
            else
            {
                // if it´s not a ctype used on a list 'new' fieldrefs are handled through the element manifest
                // we only have to handle the delete part
                _fieldrefs.Removals.ForEach(frd =>
                {
                    ctype.FieldLinks.Delete(frd.Id);
                });
            }


            Update(ctype);
        }

        private void SynchronizeFieldRefsOnListContentType(SPContentType ctype)
        {
            //sanity check
            if (ctype.IsListContentType() == false)
            {
                return;
            }

            SPList list = ctype.ParentList;
            List<SPFieldLink> updates = new List<SPFieldLink>();

            foreach (FieldRefDefinition frd in this._fieldrefs.Additions)
            {
                SPField fieldOnList;
                bool match = list.Fields
                                .FindByCriteria(field => field.Id == frd.Id)
                                .Execute(out fieldOnList);

                if (match == false)
                {                    
                    fieldOnList = list.Fields.Ensure(_parent.FindSiteColumnByDefinition(frd));

                    // TODO: check that ShowInNewForm (fe) works on "new" fields (works on updates when the field already exists)
                }

                fieldOnList.ShowInDisplayForm = frd.ShowInDisplayForm;
                fieldOnList.ShowInEditForm = frd.ShowInEditForm;
                fieldOnList.Required = frd.Required;
                fieldOnList.ShowInNewForm = frd.ShowInNewForm;
                fieldOnList.ShowInListSettings = frd.ShowInListSettings;
                fieldOnList.Title = frd.DisplayName;

                fieldOnList.Update();
                updates.Add(new SPFieldLink(fieldOnList));
            }

            // now add all the fieldrefs to the list ctype to was previously removed
            updates.ForEach(spfl =>
                {
                    ctype.FieldLinks.Add(spfl);
                });

        }

        public void SynchronizeUsages()
        {
            SPSite sitecollection = _ctype.ParentWeb.Site; // usages only work in the same sitecollection

            try
            {
                foreach (SPContentTypeUsage usage in SPContentTypeUsage.GetUsages(this._ctype).Where(spcu => spcu.IsUrlToList))
                {
                    using (SPWeb website = sitecollection.OpenWeb(usage.Url, false))
                    {
                        SPList list = website.GetList(usage.Url);

                        SPContentType ctype;
                        // note: using the ctype id of the ctype usage results in finding the wrong ctype (bug in SP?)
                        bool match = list.ContentTypes.TryFindById(this._ctype.Id, out ctype);

                        if (match == false)
                        {
                            // TODO: log this "error"
                            continue;
                        }

                        SynchronizeProperties(ctype);
                        SynchronizeFieldRefs(ctype);
                        SortFieldRefs(ctype);

                        // TODO: only sync if needed ,e.g. if we have changes, compare schemas??
                    }
                }
            }
            finally
            {
                //sitecollection.Dispose();
            }
        }

        private void Update(SPContentType ctype)
        {
            ctype.Update();
        }

        public void Synchronize()
        {
            // TODO: add logging
            // TODO: add options to sync all stuff or just some stuff via parameters
            SynchronizeProperties(this._ctype);
            SynchronizeFieldRefs(this._ctype);
            SortFieldRefs(this._ctype);
            SynchronizeUsages();
            
        }       
    }
}
